package com.autumn.zero.application.services;

import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.poi.ss.usermodel.Workbook;

import com.autumn.application.service.AbstractApplicationService;
import com.autumn.common.api.application.dto.input.AdvancedPageQueryInput;
import com.autumn.common.api.application.dto.input.AdvancedQueryInput;
import com.autumn.common.api.application.dto.input.IAdvancedPageQueryInput;
import com.autumn.common.api.application.dto.input.IAdvancedQueryInput;
import com.autumn.common.api.application.dto.input.IAdvancedSearchInput;
import com.autumn.common.api.application.dto.output.IPageQueryResult;
import com.autumn.common.api.domain.entities.IEntity;
import com.autumn.common.api.domain.entities.auditing.ISoftDelete;
import com.autumn.domain.repositories.IEntityRepository;
import com.autumn.mybatis.criterion.EntityQuery;
import com.autumn.util.ExceptionUtils;
import com.autumn.util.data.EntityQueryBuilder;
import com.autumn.util.data.PageQueryBuilder;
import com.autumn.util.excel.ExcelUtils;
import com.autumn.util.excel.WorkbookInfo;
import com.autumn.util.excel.export.GenericExportInfo;
import com.autumn.util.reflect.ReflectUtils;
import com.autumn.zero.listing.AutumnOutputZeroListingExpression;
import com.autumn.zero.listing.IAutumnZeroListingExpression;

/**
 * 查询抽象
 * 
 * @author 杨昌国 2018-11-16 17:39:48
 * @param <TPrimaryKey>
 *            主键类型
 * @param <TQueryEntity>
 *            查询实体
 * @param <TQueryRepository>
 *            查询仓储
 * @param <TQueryItemResult>
 *            查询结果类型
 */
public abstract class AbstractZeroQueryAppService<TPrimaryKey, TQueryEntity extends IEntity<TPrimaryKey>, TQueryRepository extends IEntityRepository<TQueryEntity, TPrimaryKey>, TQueryItemResult>
		extends AbstractApplicationService implements IZeroQueryAppService<TQueryItemResult> {

	/**
	 * 
	 */
	public static final int EXCEL_EXPORT_MAX_ROW_COUNT = 65000;

	/**
	 * 
	 */
	public static final int PAGE_MAX_ROW_COUNT = 200;

	private final Class<TQueryEntity> queryEntityClass;

	/**
	 * 获取查询实体类型
	 * 
	 * @return
	 */
	public final Class<TQueryEntity> getQueryEntityClass() {
		return queryEntityClass;
	}

	private final Class<TQueryItemResult> queryItemResultClass;

	/**
	 * 获取查询项目结果类型
	 * 
	 * @return
	 */
	public final Class<TQueryItemResult> getQueryItemResultClass() {
		return queryItemResultClass;
	}

	/**
	 * 
	 */
	@SuppressWarnings("unchecked")
	protected AbstractZeroQueryAppService() {
		Map<String, Class<?>> classMap = this.getGenericActualArgumentsTypeMap();
		this.queryEntityClass = (Class<TQueryEntity>) classMap.get("TQueryEntity");
		ExceptionUtils.checkNotNull(this.queryEntityClass, "queryEntityClass");
		this.queryItemResultClass = (Class<TQueryItemResult>) classMap.get("TQueryItemResult");
		ExceptionUtils.checkNotNull(this.queryItemResultClass, "queryItemResultClass");
	}

	/**
	 * 
	 * @param queryEntityClass
	 * @param queryItemResultClass
	 */
	protected AbstractZeroQueryAppService(Class<TQueryEntity> queryEntityClass,
			Class<TQueryItemResult> queryItemResultClass) {
		this.queryEntityClass = ExceptionUtils.checkNotNull(queryEntityClass, "queryEntityClass");
		this.queryItemResultClass = ExceptionUtils.checkNotNull(queryItemResultClass, "queryItemResultClass");
	}

	/**
	 * 
	 * @param queryEntityArgumentsName
	 * @param queryItemResultArgumentsName
	 */
	@SuppressWarnings("unchecked")
	protected AbstractZeroQueryAppService(String queryEntityArgumentsName, String queryItemResultArgumentsName) {
		Map<String, Class<?>> classMap = this.getGenericActualArgumentsTypeMap();
		this.queryEntityClass = (Class<TQueryEntity>) classMap.get(queryEntityArgumentsName);
		ExceptionUtils.checkNotNull(this.queryEntityClass, "queryEntityClass");
		this.queryItemResultClass = (Class<TQueryItemResult>) classMap.get(queryItemResultArgumentsName);
		ExceptionUtils.checkNotNull(this.queryItemResultClass, "queryItemResultClass");
	}

	private Map<String, Class<?>> genericActualArgumentsTypeMap = null;

	/**
	 * 获取泛型参数类型Map
	 * 
	 * @return
	 */
	protected final Map<String, Class<?>> getGenericActualArgumentsTypeMap() {
		synchronized (this) {
			if (this.genericActualArgumentsTypeMap == null) {
				this.genericActualArgumentsTypeMap = ReflectUtils.getGenericActualArgumentsTypeMap(this.getClass());
			}
			return this.genericActualArgumentsTypeMap;
		}
	}

	/**
	 * 获取查询仓储
	 * 
	 * @return
	 */
	protected abstract TQueryRepository getQueryRepository();

	/**
	 * 查询排序处理
	 * 
	 * @param query
	 *            查询
	 */
	protected void queryByOrder(EntityQuery<TQueryEntity> query) {
		query.orderBy(IEntity.FILED_ID);
	}

	/**
	 * 是否是软删除查询实体
	 * 
	 * @return
	 */
	public final boolean isSoftDeleteQueryEntity() {
		return ISoftDelete.class.isAssignableFrom(this.getQueryEntityClass());
	}

	/**
	 * 系统条件处理
	 * 
	 * @param query
	 *            查询
	 */
	protected void systemByCriteria(EntityQuery<TQueryEntity> query) {
		if (this.isSoftDeleteQueryEntity()) {
			query.eq(ISoftDelete.FILED_IS_DELETE, false);
		}
	}

	/**
	 * 获取搜索成员
	 * 
	 * @return
	 */
	protected abstract Set<String> getSearchMembers();

	/**
	 * 获取Excel导出的最大行数
	 * 
	 * @return
	 */
	protected int getExcelExportMaxRowCount() {
		return EXCEL_EXPORT_MAX_ROW_COUNT;
	}

	/**
	 * 获取页的最大行数
	 * 
	 * @return
	 */
	protected int getPageMaxRowCount() {
		return PAGE_MAX_ROW_COUNT;
	}

	/**
	 * 查询输入条件
	 * 
	 * @param query
	 *            查询
	 * @param input
	 *            输入
	 */
	protected void queryInputCriteria(EntityQuery<TQueryEntity> query, IAdvancedSearchInput input) {

	}

	/**
	 * 创建实体查询
	 * 
	 * @param input
	 *            输入
	 * @return
	 */
	private EntityQueryBuilder<TQueryEntity> createEntityQuery(IAdvancedQueryInput input) {
		if (input == null) {
			input = new AdvancedQueryInput();
		}
		EntityQueryBuilder<TQueryEntity> queryBuilder = new EntityQueryBuilder<>(this.getQueryEntityClass());
		this.systemByCriteria(queryBuilder.getQuery());
		this.queryInputCriteria(queryBuilder.getQuery(), input);
		queryBuilder.searchMembers(input.getSearchKeyword(), this.getSearchMembers());
		queryBuilder.criteriaByItem(input.getCriterias());
		this.queryByOrder(queryBuilder.getQuery());
		return queryBuilder;
	}

	@Override
	public List<TQueryItemResult> queryByList(IAdvancedQueryInput input) {
		EntityQueryBuilder<TQueryEntity> queryBuilder = this.createEntityQuery(input);
		return queryBuilder.toResult(getQueryRepository(), this.getQueryItemResultClass());
	}

	@Override
	public IPageQueryResult<TQueryItemResult> queryByPageList(IAdvancedPageQueryInput input) {
		if (input == null) {
			input = new AdvancedPageQueryInput();
		}
		PageQueryBuilder<TQueryEntity> queryBuilder = new PageQueryBuilder<>(this.getQueryEntityClass());
		this.systemByCriteria(queryBuilder.getQuery());
		this.queryInputCriteria(queryBuilder.getQuery(), input);
		queryBuilder.searchMembers(input.getSearchKeyword(), this.getSearchMembers()).page(input,
				this.getPageMaxRowCount());
		this.queryByOrder(queryBuilder.getQuery());
		return queryBuilder.toPageResult(getQueryRepository(), this.getQueryItemResultClass());
	}

	@Override
	public Workbook exportByExcel(IAdvancedQueryInput input) {
		EntityQueryBuilder<TQueryEntity> queryBuilder = this.createEntityQuery(input);
		TQueryRepository repository = getQueryRepository();
		int count = queryBuilder.toCount(repository);
		int maxRowCount = this.getExcelExportMaxRowCount();
		if (count > maxRowCount) {
			ExceptionUtils.throwValidationException(String.format("需要导出的数据超过 %s 行，请通过条件限制。", maxRowCount));
		}
		List<TQueryItemResult> items = queryBuilder.toResult(repository, this.getQueryItemResultClass());
		GenericExportInfo<TQueryItemResult> exportInfo = new GenericExportInfo<>(this.getQueryItemResultClass());
		exportInfo.setItems(items);
		WorkbookInfo workbookInfo = ExcelUtils.getWorkbookInfo(this.getQueryItemResultClass(), this.getModuleName());
		return workbookInfo.createExportWorkbook(exportInfo, false);
	}

	@Override
	public IAutumnZeroListingExpression createListingExpression() {
		return new AutumnOutputZeroListingExpression<TQueryEntity, TQueryRepository, TQueryItemResult>(
				this.getQueryEntityClass(), this.getQueryRepository(), this.getQueryItemResultClass(), query -> {
					systemByCriteria(query);
					queryByOrder(query);
				});
	}
}
